Suppose a string x declared in main that is used as an argument for a function helper(x : String).
helper(x) in Java, we give helper a reference to x.helper(x) in Rust, we give helper ownership of x.On Rust’s Memory Management:
- Unlike malloc and free, memory management is about using and not using variables.
- When a function returns, all the data it owns gets dropped.
Let’s look at this Rust code:
fn main() {
let x = format!("Hello World");
helper(x);
helper(x);
}
fn helper(x : String) {
println!(x);
}This code cannot compile because when we call helper the first time, we give ownership of x to it; and so when helper returns, it drops x. As a result, because we try to use x on the second helper(x), we get error[E0382]: use of moved value
Let’s look at this Java code:
void main() {
Vector x = ...;
helper(x);
helper(x);
}
void helper(Vector x) {
println!(x);
}In Java, we give access (references) to things rather than ownership, which entangles their states. When helper modifies x in Java, it’s immediately visible to main.
Moreover, we can never know when to free memory; even if main exits, due to the possibility of helper(x) making threads.
Non-Copyable: Values move from place to place.
Clone: Run custom code to copy a value.
fn main() {
let x = format!("Hello World");
helper(x.clone());
helper(x);
}Copy: Some basic data types are automatically cloned when you use them, as it’s very cheap.
You can never have a reader/writer at the same time.
Both locks last until their reference goes out of scope.
Shared Borrow (&x): We can borrow variables to create a reference.
More on Borrowed == Immutable:
Mutation is allowed:
- In controlled scenarios with specific APIs (e.g., mutex), or
- When a
mutvalue is shared borrowed, it can’t be mutated during the borrow—but regains mutability afterward. (see “Example: Immutability of Shared Borrows”)
As seen in the previous example, this code doesn’t compile.
fn main() {
let x = format!("Hello World");
helper(x);
helper(x);
}
fn helper(x : String) {
println!(x);
}In this fix, we borrow the string to create a reference and update helper to take a reference to a string.
fn main() {
let x = format!("Hello World");
let r = &x;
helper(r);
helper(r);
}
fn helper(x : &String) {
println!(x);
}r is a copy type!This is the code from the previous example, with a key change: We made made x mutable, and even push a character to it.
Shouldn’t this not compile because shared referrences are immutable?
fn main() {
let mut x = format!("Hello World");
x.push("a");
let r = &x;
helper(r);
helper(r);
}
fn helper(x : &String) {
println!("{}", x);
}The reason the code compiles is that Rust sees x as mutable (as we declared it), until it gets borrowed.
r exists, we’ll get errors.// Does not compile (borrowed == immutable)
fn main() {
let mut x = format!("Hello World");
let r = &x;
helper(r);
helper(r);
x.push("a");
}// Does compile
fn main() {
let mut x = format!("Hello World");
{
let r = &x;
helper(r);
helper(r);
}
x.push("a");
}x once r is out of scope.Mutable Borrows (&mut x):
This Rust code uses borrowing to concatenate two strings:
pub fn main() {
let (mut str1, str2) = two_words();
str1 = join_words(str1, str2);
println!("concatenated string is {:?}", str1);
}
fn two_words() -> (String, String) {
(format!("fellow"), format!("Rustaceans"))
}
fn join_words(mut prefix: String, suffix: String) -> String {
prefix.push(' ');
for ch in suffix.chars() {
prefix.push(ch);
}
prefix
}This is a modification of the code to use borrowing and modify str1 in place:
pub fn main() {
let (mut str1, str2) = two_words();
join_words(&mut str1, &str2);
println!("concatenated string is {:?}", str1);
}
fn two_words() -> (String, String) {
(format!("fellow"), format!("Rustaceans"))
}
fn join_words(prefix: &mut String, suffix: &String) {
prefix.push(' ');
for ch in suffix.chars() {
prefix.push(ch);
}
}